Para acceder a la Interactive Python Notebook (ipynb) correspondiente a esta página HTML, haga click en el siguiente link.
Esta notebook contiene algunas modificaciones surgidas a partir de la presentación del día 30/5/2024.
Para acceder a la versión anterior de esta notebook, utilizada el día de la presentación, hacer click en este link.
Para acceder a la información descriptiva de los cambios realizados entre ambas versiones, hacer click en este link.

Trabajo Práctico¶

Visualización de la Información - Inside Airbnb¶

Comenzamos con un análisis básico del dataset que se encuentra en el siguiente link.


Cargamos las librerías y hacemos las configuraciones que vamos a necesitar:
In [1]:
# IMPORTANTE!!!

# Si desea ejecutar esta notebook en forma local, puede instalar todo lo necesario con:
# pip install notebook pandas altair folium vegafusion-python-embed vegafusion vl-convert-python

# Si desea ejecutar esta notebook desde Github Codespaces, puede instalar todo lo necesario con:
# pip install altair folium vegafusion-python-embed vegafusion vl-convert-python

import pandas as pd
import altair as alt
import folium
from folium.plugins import HeatMap
import glob
import os
In [2]:
alt.data_transformers.enable("vegafusion")
Out[2]:
DataTransformerRegistry.enable('vegafusion')

Limpieza inicial de datos¶

Abrimos el archivo y observamos los datos:

In [3]:
df = pd.read_csv('listings.csv', low_memory = False)
In [4]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 36561 entries, 0 to 36560
Data columns (total 18 columns):
 #   Column                          Non-Null Count  Dtype  
---  ------                          --------------  -----  
 0   id                              36561 non-null  int64  
 1   name                            36561 non-null  object 
 2   host_id                         36561 non-null  int64  
 3   host_name                       36561 non-null  object 
 4   neighbourhood_group             0 non-null      float64
 5   neighbourhood                   36561 non-null  object 
 6   latitude                        36561 non-null  float64
 7   longitude                       36561 non-null  float64
 8   room_type                       36561 non-null  object 
 9   price                           33911 non-null  float64
 10  minimum_nights                  36561 non-null  int64  
 11  number_of_reviews               36561 non-null  int64  
 12  last_review                     29679 non-null  object 
 13  reviews_per_month               29679 non-null  float64
 14  calculated_host_listings_count  36561 non-null  int64  
 15  availability_365                36561 non-null  int64  
 16  number_of_reviews_ltm           36561 non-null  int64  
 17  license                         440 non-null    object 
dtypes: float64(5), int64(7), object(6)
memory usage: 5.0+ MB
In [5]:
df.describe()
Out[5]:
id host_id neighbourhood_group latitude longitude price minimum_nights number_of_reviews reviews_per_month calculated_host_listings_count availability_365 number_of_reviews_ltm
count 3.656100e+04 3.656100e+04 0.0 36561.000000 36561.000000 3.391100e+04 36561.000000 36561.000000 29679.000000 36561.000000 36561.000000 36561.000000
mean 6.325924e+17 2.032641e+08 NaN -34.591300 -58.417899 5.954551e+04 5.605454 23.255190 1.455364 18.978830 215.180794 10.225158
std 4.442096e+17 1.929296e+08 NaN 0.018490 0.030271 7.015697e+05 23.071708 39.888967 1.409139 43.477847 117.459621 14.629388
min 1.150800e+04 1.342600e+04 NaN -34.693700 -58.530890 2.600000e+02 1.000000 0.000000 0.010000 1.000000 0.000000 0.000000
25% 4.289224e+07 2.807387e+07 NaN -34.602477 -58.437889 2.273000e+04 1.000000 1.000000 0.430000 1.000000 113.000000 0.000000
50% 8.212314e+17 1.339204e+08 NaN -34.590704 -58.419440 3.059800e+04 2.000000 8.000000 1.040000 2.000000 241.000000 4.000000
75% 1.007852e+18 3.947047e+08 NaN -34.580730 -58.392992 4.371200e+04 4.000000 28.000000 2.090000 13.000000 331.000000 15.000000
max 1.144128e+18 5.744205e+08 NaN -34.534980 -58.355403 4.371150e+07 1000.000000 813.000000 53.470000 294.000000 365.000000 608.000000


Hacemos una primera limpieza de datos:

In [6]:
len(df)
Out[6]:
36561
In [7]:
# Se eliminan los registros que no tienen precio.
df = df.dropna(subset=['price'])
In [8]:
len(df)
Out[8]:
33911
In [9]:
# Se elimina esta columna, que para el caso de CABA no contiene datos
df = df.drop(columns=['neighbourhood_group'])

Algunas visualizaciones preliminares de los datos¶

Boxplot de los precios:

In [10]:
# Creamos un boxplot para la columna 'price'
boxplot = alt.Chart(df).mark_boxplot().encode(
    x='price'
).properties(
    title='Boxplot de precios'
)
boxplot = boxplot.properties(width=1024, height=100)
boxplot.show()


Se ven varios valores ridículos que claramente son errores de carga o algún problema similar. Creamos un dataframe alternativo eliminando los outliers. Lo llamamos df_limpio. Lo usaremos en los gráficos en los que corresponda de acuerdo al análisis que estemos haciendo.

In [11]:
# Cálculos de cuartiles y distancia intercuartil
Q1 = df['price'].quantile(0.25)
Q3 = df['price'].quantile(0.75)
IQR = Q3 - Q1

# Calculamos los límites para los outliers
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
In [12]:
# Calculamos el nuevo dataframe sin los outliers
df_limpio = df[(df['price'] >= lower_bound) & (df['price'] <= upper_bound)]


Graficamos nuevamente el boxplot, ahora sin los outliers:

In [13]:
# Creamos un boxplot para la columna 'price'
boxplot = alt.Chart(df_limpio).mark_boxplot().encode(
    x='price'
).properties(
    title='Boxplot de precios (sin outliers)'
)

boxplot = boxplot.properties(width=1024, height=100)
boxplot.show()


Hacemos un histograma de precios:

In [14]:
# Creamos un histograma para la columna 'price'
limite = 150000
bins = 25

histogram = alt.Chart(df[df['price'] <= limite]).mark_bar().encode(
    alt.X('price', bin=alt.Bin(maxbins=bins), title='Precio'),
    alt.Y('count()', title='Cantidad', axis=alt.Axis(title=None, ticks=False, labels=False)),
    color='room_type:N'
).properties(
    title='Histograma de precios'
)

histogram = histogram.properties(width=950)
histogram.show()


Datos de disponibilidad:

In [15]:
histogram = alt.Chart(df[df['price'] <= limite]).mark_bar().encode(
    alt.X('availability_365', bin=alt.Bin(maxbins=bins), title='Días de disponibilidad'),
    alt.Y('count()', title='Cantidad', axis=alt.Axis(title=None, ticks=False, labels=False))
).properties(
    title='Histograma de disponibilidad'
)

histogram = histogram.properties(width=1024)
histogram.show()


Tiramos preliminarmente los datos de coordenadas haciendo un scatter-plot, para ver que sean coherentes:

In [16]:
# Creamos scatter-plot a partir de las latitudes y longitudes
map_chart = alt.Chart(df).mark_circle().encode(
    longitude='longitude:Q',
    latitude='latitude:Q',
    size=alt.value(5)
).properties(
    title='Scatter-plot de latitudes y longitudes'
)

map_chart = map_chart.properties(width=1024, height=1024)
map_chart.show()

Se percibe la forma característica de CABA. También se observa una densidad de registros mucho mayor en las zonas cercanas al bajo y Av. Del Libertador.

Ubicamos nuevamente los puntos, ahora con la ayuda visual de una capa geográfica interactiva (provista por la librería folium). La misma permite hacer zoom, desplazarse y definir un Tooltip para cada registro con los datos de interés. También se usaron colores para codificar la categoría de cada registro.

In [17]:
# Colores a usar para cada tipo de habitación
color_mapping = {
    'Entire home/apt': 'blue',
    'Private room': 'green',
    'Shared room': 'red',
    'Hotel room': 'purple'
}
obelisco = (-34.603698, -58.381625)
parquecentenario = (-34.606455, -58.435567)

# Creamos mapa centrado en el obelisco
mymap = folium.Map(location=parquecentenario, zoom_start=13)

# Agregamos un marcador con su tooltip para cada registro
for index, row in df.iterrows():
    tooltip = folium.Tooltip(f"Precio: ${row['price']}<br>Barrio: {row['neighbourhood']}<br>Categoría: {row['room_type']}<br>Contacto: {row['host_name']}<br>Reseñas: {row['number_of_reviews']}")
    folium.CircleMarker(
        location=[row['latitude'], row['longitude']],
        radius=2,
        color=None,
        fill=True,
        fill_color=color_mapping[row['room_type']],
        fill_opacity=0.6,
        tooltip=tooltip
    ).add_to(mymap)

mymap
Out[17]:
Make this Notebook Trusted to load map: File -> Trust Notebook

    Entire home/apt
    Private room
    Shared room
    Hotel room

Se aprecia a simple vista que la gran mayoría de los puntos son azules (casas o departamentos enteros de uso exclusivo). Por eso, observaremos la cantidad relativa de cada una de las 4 opciones disponibles:

In [18]:
# Creamos gráfico de barras horizontales
room_type_counts = df['room_type'].value_counts().reset_index()
bar_chart = alt.Chart(room_type_counts).mark_bar().encode(
    y=alt.Y('room_type:N', axis=alt.Axis(title=None, labels=False), sort='-x'),
    x=alt.X('count:Q', axis=alt.Axis(title=None, labels=False)),
    color=alt.Color('room_type:N', scale=alt.Scale(domain=list(color_mapping.keys()), range=list(color_mapping.values()))),
    tooltip=['room_type', 'count']
)

# Ponemos los valores de las cantidades a la derecha de cada barra
text = bar_chart.mark_text(align='left', baseline='middle', dx=3).encode(text='count:Q')

# Combinamos ambos gráficos en uno
bar_chart_with_text = (bar_chart + text).properties(
    title='Cantidad de registros por categoría de room_type'
)
bar_chart_with_text = bar_chart_with_text.properties(width=950, height=150)
bar_chart_with_text.show()


Efectivamente, la gran mayoría son casas o departamentos completos disponibles.

Veamos también las distribuciones de precio para ver si las distintas categorías son muy diferentes. Los siguientes Boxplots paralelos ilustran esto:

In [19]:
# Creamos boxplots
chart = alt.Chart(df_limpio).mark_boxplot(size=50, outliers={'size': 1}).encode(
    x=alt.X('room_type:N', title=None, axis=alt.Axis(labels=False)),
    y=alt.Y('price:Q', title='Precio ($)'),
    color=alt.Color('room_type:N', scale=alt.Scale(domain=list(color_mapping.keys()), range=list(color_mapping.values()))),
).properties(
    title='Distribución de precios por tipo de habitación'
)
chart = chart.properties(width=800, height=300)
chart.show()

Como era de esperar, la opción altamente predominante que prefiere la gente es la más cara. Le siguen las habitaciones privadas dentro de casas o departamentos (en general con baño y cocina compartidos), luego las habitaciones de hotel (los hoteles de mayor categoría no suelen ofrecerse por Airbnb), y finalmente las habitaciones compartidas.



Algunas historias...¶

Analizaremos las siguientes temáticas:
1) Evolución del perfil de los dueños.
2) Relación entre oferta, precios y presencia de zonas turísticas.
3) Análisis característico por barrio.


1) Evolución de perfil de los dueños¶

Algunas preguntas a responder:
•¿Cómo evolucionó el perfil de los dueños en el último año?
•¿Se pueden hacer visualizaciones que permitan intuir los motivos de la evolución de esos perfiles?
•¿Hay factores coyunturales que influyan?

El objetivo es determinar cómo se fue modificando el perfil de los dueños que ofertan sus propiedades a lo largo de los últimos 12 meses. Como primer paso generamos un único dataframe con todos los datos, los cuales se pueden descargar para cada mes de los siguientes links al día de la fecha: 2024-04, 2024-03, 2024-02, 2024-01, 2023-12, 2023-11, 2023-10, 2023-09, 2023-08, 2023-07, 2023-06 y 2023-05.

In [20]:
# Directorio con los CSV ya descargados
csv_files = glob.glob("12meses/*.csv")

# Leemos y combinamos los CSV
dataframes = []
for file in csv_files:
    df_mes = pd.read_csv(file, low_memory = False)
    df_mes['periodo'] = os.path.splitext(os.path.basename(file))[0]
    dataframes.append(df_mes)

# Guardamos todo en un único dataframe
df_12meses = pd.concat(dataframes, ignore_index=True)

# Cotizaciones dólar oficial
dollar = {'2023-05': 244.50, '2023-06': 266.50, '2023-07': 284.00, '2023-08': 365.50,
          '2023-09': 365.50, '2023-10': 365.50, '2023-11': 375.00, '2023-12': 826.75,
          '2024-01': 844.50, '2024-02': 859.50, '2024-03': 876.00, '2024-04': 894.00}
#(inside-airbnb convirtió publicaciones originales en dólares a pesos usando el dólar oficial).
# (Las volvemos a convertir a dólares para evitar las distorsiones de la inflación en pesos)

# Agregamos columna de precio en dólares
df_12meses['dollar'] = df_12meses['periodo'].map(dollar)
df_12meses['price_usd'] = df_12meses['price'] / df_12meses['dollar']
df_12meses.drop(columns=['dollar'], inplace=True)

# Eliminamos columna sin datos
df_12meses.drop(columns=['neighbourhood_group'], inplace=True)

# Eliminamos precios fuera de rango
df_12meses = df_12meses[(df_12meses['price'] >= 0) & (df_12meses['price'] <= df_12meses['price'].quantile(0.99))]


Graficamos la evolución de ofertas en el último año:

In [21]:
# Contamos registros
agg_data = df_12meses['periodo'].value_counts().reset_index()
agg_data.columns = ['periodo', 'count']

# Creamos el gráfico
line_chart = alt.Chart(agg_data).mark_line().encode(
    x=alt.X('periodo:T', title='Período [Año-Mes]', axis=alt.Axis(format='%Y-%m')),
    y=alt.Y('count:Q', title='Cantidad de ofertas', scale=alt.Scale(domain=[22000, 35000]))
).properties(
    title='Evolución de ofertas en el último año'
)

line_chart = line_chart.properties(width=800, height=300)
line_chart.show()


Graficamos la evolución del precio (promedio y mediana) en el último año:

In [22]:
# Agrupamos registros
agg_data = df_12meses.groupby('periodo')['price_usd'].mean().reset_index(name='average_price_usd')

# Creamos el gráfico
line_chart = alt.Chart(agg_data).mark_line(color='darkgreen').encode(
    x=alt.X('periodo:T', title='Período [Año-Mes]', axis=alt.Axis(format='%Y-%m')),
    y=alt.Y('average_price_usd:Q', title='Precio medio [USD]', scale=alt.Scale(domain=[35, 55]))
).properties(
    title='Evolución del precio medio en el último año'
)

line_chart = line_chart.properties(width=800, height=300)
line_chart.show()
In [23]:
# Agrupamos registros
agg_data = df_12meses.groupby('periodo')['price_usd'].median().reset_index(name='median_price_usd')

# Creamos el gráfico
line_chart = alt.Chart(agg_data).mark_line(color='darkgreen').encode(
    x=alt.X('periodo:T', title='Período [Año-Mes]', axis=alt.Axis(format='%Y-%m')),
    y=alt.Y('median_price_usd:Q', title='Madiana del precio [USD]', scale=alt.Scale(domain=[30, 40]))
).properties(
    title='Evolución de la mediana del precio en el último año'
)

line_chart = line_chart.properties(width=800, height=300)
line_chart.show()


La cantidad mínima de noches de hospedaje es uno de los parámetros que puede especificar el propietario. Suele relacionarse con las exigencias del mismo a la hora de alquilar. Hacemos el gráfico correspondiente:

In [24]:
# Calculamos promedios
avg_price_per_period = df_12meses.groupby('periodo', as_index=False)['minimum_nights'].mean()

# Creamos el gráfico
chart = alt.Chart(avg_price_per_period).mark_bar().encode(
    x=alt.X('periodo:N', title='Período [Año-Mes]', axis=alt.Axis(labelAngle=0)),
    y=alt.Y('minimum_nights:Q', title='Mínima cantidad de noches'),
    tooltip=['periodo', 'minimum_nights']
).properties(
    title='Evolución de la mínima cantidad de noches en el último año'
)

chart = chart.properties(width=600, height=300)
chart
Out[24]:


Mostramos finalmente la evolución de las pretensiones de los locadores de acuerdo a cómo se fueron modificando el precio y la exigencia de la mínima cantidad de noches de hospedaje a lo largo del último año:

In [25]:
# Calculamos promedios
avg_values_per_period = df_12meses.groupby('periodo', as_index=False).agg({
    'price_usd': 'mean',
    'minimum_nights': 'mean'
})

# Ordenamos
avg_values_per_period = avg_values_per_period.sort_values(by='periodo')

# Creamos scatter plot
scatter_plot = alt.Chart(avg_values_per_period).mark_circle(size=60).encode(
    x=alt.X('price_usd:Q', title='Precio promedio [USD]', scale=alt.Scale(domain=[40, 54])),
    y=alt.Y('minimum_nights:Q', title='Promedio de noches mínimas de hospedaje exigidas', scale=alt.Scale(domain=[4.4, 6.6])),
    tooltip=['periodo', 'price_usd', 'minimum_nights'],
    color='periodo:N'
).properties(
    title='Evolución de las exigencias de los propietarios'
)

# Creamos lineas para interconectar puntos
line_chart = alt.Chart(avg_values_per_period).mark_line().encode(
    x='price_usd:Q',
    y='minimum_nights:Q',
    order='periodo:N'
)

# Creamos la flechita que indica la dirección
origin = avg_values_per_period[avg_values_per_period['periodo'] == '2023-12'][['price_usd', 'minimum_nights']].values[0]
destination = avg_values_per_period[avg_values_per_period['periodo'] == '2024-01'][['price_usd', 'minimum_nights']].values[0]
midpoint = [(origin[0] + destination[0]) / 2, 1.02 *(origin[1] + destination[1]) / 2]
angle = -90 + 180*(destination[0] > origin[0])
arrowhead = alt.Chart(pd.DataFrame({'x': [midpoint[0]], 'y': [midpoint[1]], 'angle': [angle]})).mark_point(
            shape='arrow', size=500, filled=True, color='black').encode(x='x:Q', y='y:Q', angle=alt.value(angle))

chart = (scatter_plot + line_chart + arrowhead).properties(width=600, height=400)
chart
Out[25]:


Para ver cómo se relaciona esta baja en las pretensiones de los propietarios con la demanda, calculamos la disponibilidad promedio a lo largo del último año:

In [26]:
# Promediamos
agg_data = df_12meses.groupby('periodo')['availability_365'].mean().reset_index(name='average_availability_365')

# Calculamos el gráfico de barras
bar_chart = alt.Chart(agg_data).mark_bar().encode(
    x=alt.X('periodo:N', title='Período [Año-Mes]', axis=alt.Axis(labelAngle=0)),
    y=alt.Y('average_availability_365:Q', title='Promedio de disponibilidad (sobre 365 días)')
).properties(
    width=500, height=300, title='Evolución de la disponibilidad de las propiedades en el último año')

bar_chart = bar_chart.properties(width=600)
bar_chart.show()


La disponibilidad está en máximos del último año. La demanda pareciera que no subió a la par de la oferta, tiene sentido que los precios bajen y se acepten hospedajes más cortos de los que se aceptaban antes.

2) Relación entre oferta, precios y presencia de zonas turísticas¶

Algunas preguntas a responder:
•¿Existe una correlación entre el precio y la proximidad a puntos turísticos?
•¿Existe una correlación entre la cantidad de ofertas y la proximidad a puntos turísticos?
•¿Cuál es la mejor zona para alojarse?


Mostramos inicialmente la cantidad de Airbnbs y puntos de interés (POIs) turísticos por barrio:

In [27]:
%%HTML
<div class='tableauPlaceholder' id='viz1717032696186' style='position: relative'><noscript><a href='#'><img alt='Airbnbs por barrio ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;Airbnbsporbarrio&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz'  style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='Airbnb-Tpinfovis&#47;Airbnbsporbarrio' /><param name='tabs' value='no' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;Airbnbsporbarrio&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div>                <script type='text/javascript'>                    var divElement = document.getElementById('viz1717032696186');                    var vizElement = divElement.getElementsByTagName('object')[0];                    vizElement.style.width='100%';vizElement.style.height=(divElement.offsetWidth*0.75)+'px';                    var scriptElement = document.createElement('script');                    scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js';                    vizElement.parentNode.insertBefore(scriptElement, vizElement);                </script>
In [28]:
%%HTML
<div class='tableauPlaceholder' id='viz1717032768426' style='position: relative'><noscript><a href='#'><img alt='POIs por barrio ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;POIsporbarrio&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz'  style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='Airbnb-Tpinfovis&#47;POIsporbarrio' /><param name='tabs' value='no' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;POIsporbarrio&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div>                <script type='text/javascript'>                    var divElement = document.getElementById('viz1717032768426');                    var vizElement = divElement.getElementsByTagName('object')[0];                    vizElement.style.width='100%';vizElement.style.height=(divElement.offsetWidth*0.75)+'px';                    var scriptElement = document.createElement('script');                    scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js';                    vizElement.parentNode.insertBefore(scriptElement, vizElement);                </script>


A modo de ejemplo, el siguiente mapa muestra la ubicación geográfica de los principales POI del rubro gastronomía:

In [29]:
%%HTML
<div class='tableauPlaceholder' id='viz1717032845165' style='position: relative'><noscript><a href='#'><img alt='Mapa gastronomico de CABA ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;Mapagastronomico&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz'  style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='Airbnb-Tpinfovis&#47;Mapagastronomico' /><param name='tabs' value='no' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;Mapagastronomico&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div>                <script type='text/javascript'>                    var divElement = document.getElementById('viz1717032845165');                    var vizElement = divElement.getElementsByTagName('object')[0];                    vizElement.style.width='100%';vizElement.style.height=(divElement.offsetWidth*0.75)+'px';                    var scriptElement = document.createElement('script');                    scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js';                    vizElement.parentNode.insertBefore(scriptElement, vizElement);                </script>


El siguiente mapa muestra la información combinada:

In [30]:
%%HTML
<div class='tableauPlaceholder' id='viz1717032946858' style='position: relative'><noscript><a href='#'><img alt='Mapa multicapa - Airbnb, Gastronomia, Atracciones y Barrios ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;Mapamulticapa&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz'  style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='Airbnb-Tpinfovis&#47;Mapamulticapa' /><param name='tabs' value='no' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;Ai&#47;Airbnb-Tpinfovis&#47;Mapamulticapa&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div>                <script type='text/javascript'>                    var divElement = document.getElementById('viz1717032946858');                    var vizElement = divElement.getElementsByTagName('object')[0];                    vizElement.style.width='100%';vizElement.style.height=(divElement.offsetWidth*0.75)+'px';                    var scriptElement = document.createElement('script');                    scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js';                    vizElement.parentNode.insertBefore(scriptElement, vizElement);                </script>

3) Análisis característico por barrio¶

Algunas preguntas a responder:
•¿Los precios de los Airbnb varían significativamente entre los diferentes barrios de Buenos Aires?
•¿Cuál es la relación entre precio y cantidad de ofertas para los distintos barrios?
•¿Cuál es la distribución geográfica de las propiedades ofrecidas? ¿Cómo se distribuyen las más caras y las más baratas geográficamente?

Graficamos la oferta y los precios medios por cada barrio:

In [31]:
# Creamos gráfico de barras para la cantidad de registros por barrio.
bar_chart = alt.Chart(df).mark_bar().encode(
    y=alt.Y('neighbourhood:N', sort='-x', title='Barrio'),
    x=alt.X('count():Q', title='Cantidad de registros')
).properties(
    title='Cantidad de registros por barrio'
)
bar_chart = bar_chart.configure_axis(labelFontSize=12, titleFontSize=14)

bar_chart = bar_chart.properties(width=1024, height=650)
bar_chart.show()


Agregamos un scatter-plot con precios promedios y cantidad de registros por barrio

In [32]:
# Calculamos el precio promedio por barrio
bar_chart = alt.Chart(df_limpio.groupby('neighbourhood')['price'].mean().reset_index()).mark_bar(color='darkseagreen').encode(
    y=alt.X('neighbourhood', sort='-x', title='Barrio'),
    x=alt.X('price', title='Precio')
).properties(
    title='Precio promedio por barrio', width=1024)
bar_chart = bar_chart.configure_axis(labelFontSize=12, titleFontSize=14)
bar_chart
Out[32]:
In [33]:
%%HTML
<div class='tableauPlaceholder' id='viz1717021753542' style='position: relative'><noscript><a href='#'><img alt='Dashboard 1 ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;bu&#47;burbujas_barrio&#47;Dashboard1&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz'  style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='burbujas_barrio&#47;Dashboard1' /><param name='tabs' value='no' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;bu&#47;burbujas_barrio&#47;Dashboard1&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div>                <script type='text/javascript'>                    var divElement = document.getElementById('viz1717021753542');                    var vizElement = divElement.getElementsByTagName('object')[0];                    if ( divElement.offsetWidth > 800 ) { vizElement.style.width='1000px';vizElement.style.height='827px';} else if ( divElement.offsetWidth > 500 ) { vizElement.style.width='1000px';vizElement.style.height='827px';} else { vizElement.style.width='100%';vizElement.style.height='727px';}                     var scriptElement = document.createElement('script');                    scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js';                    vizElement.parentNode.insertBefore(scriptElement, vizElement);                </script>


Estos son los barrios que concentran la mayor oferta de Airbnbs:

In [34]:
%%HTML
<div class='tableauPlaceholder' id='viz1717021434025' style='position: relative'><noscript><a href='#'><img alt='Cantidad de departamentos por barrio ' src='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;an&#47;analisis_airbnb_dep_barrio&#47;ubicacionpubcaras&#47;1_rss.png' style='border: none' /></a></noscript><object class='tableauViz'  style='display:none;'><param name='host_url' value='https%3A%2F%2Fpublic.tableau.com%2F' /> <param name='embed_code_version' value='3' /> <param name='site_root' value='' /><param name='name' value='analisis_airbnb_dep_barrio&#47;ubicacionpubcaras' /><param name='tabs' value='no' /><param name='toolbar' value='yes' /><param name='static_image' value='https:&#47;&#47;public.tableau.com&#47;static&#47;images&#47;an&#47;analisis_airbnb_dep_barrio&#47;ubicacionpubcaras&#47;1.png' /> <param name='animate_transition' value='yes' /><param name='display_static_image' value='yes' /><param name='display_spinner' value='yes' /><param name='display_overlay' value='yes' /><param name='display_count' value='yes' /><param name='language' value='en-US' /></object></div>                <script type='text/javascript'>                    var divElement = document.getElementById('viz1717021434025');                    var vizElement = divElement.getElementsByTagName('object')[0];                    vizElement.style.width='100%';vizElement.style.height=(divElement.offsetWidth*0.75)+'px';                    var scriptElement = document.createElement('script');                    scriptElement.src = 'https://public.tableau.com/javascripts/api/viz_v1.js';                    vizElement.parentNode.insertBefore(scriptElement, vizElement);                </script>


Hacemos un scatter-plot que muestre la relación entre precio y cantidad de ofertas:

In [35]:
# Scatter-plot precios vs oferta por barrio
agg_data = df_limpio.groupby('neighbourhood').agg(
    average_price=('price', 'mean'),
    count=('price', 'size')
).reset_index()

# Creamos el scatter plot
scatter_plot = alt.Chart(agg_data).mark_point(filled=True, size=80).encode(
    x=alt.X('average_price:Q', title='Precio promedio'),
    y=alt.Y('count:Q', title='Cantidad de viviendas ofrecidas'),
    color=alt.Color('neighbourhood:N', title='Barrio'),
    tooltip=[
        alt.Tooltip('neighbourhood:N', title='Barrio:'),
        alt.Tooltip('average_price:Q', title='Precio promedio:', format='$.0f'),
        alt.Tooltip('count:Q', title='Cantidad:')
    ]).properties(
    title='Precio promedio vs. cantidad de oferta para cada barrio'
)
scatter_plot = scatter_plot.properties(width=900, height=400)
scatter_plot.show()


Se aprecia una gran oferta en Palermo y Recoleta, donde los precios ya relativamente altos de esos barrios se acrecientan aun más por la cercanía a puntos turísticos. Otros barrios cuyas propiedades no suelen tener valores altos en el mercado inmobiliario, como San Telmo y La Boca, presentan mejores precios para alquileres temporarios por su cercanía a puntos turísticos.


Agregamos finalmente dos mapas de calor correspondientes a las ofertas cuyo precio se encuentra en los deciles extremos. Y un mapa final con la totalidad de la oferta como referencia.


Mapa de calor del 10% más barato de las propiedades ofrecidas:

In [36]:
# Zoom y centro iniciales del mapa
map_center = [df['latitude'].mean(), df['longitude'].mean()]
base_map = folium.Map(location=map_center, zoom_start=13)

# Nos quedamos con el 10% más barato
baratos = df[df['price'] <= df['price'].quantile(0.10)]
heat_data = baratos[['latitude', 'longitude']].values.tolist()

# Creamos el mapa
HeatMap(heat_data, radius=5, blur=4, max_zoom=1).add_to(base_map)
base_map
Out[36]:
Make this Notebook Trusted to load map: File -> Trust Notebook


Mapa de calor del 10% más caro de las propiedades ofrecidas:

In [37]:
# Zoom y centro iniciales del mapa
map_center = [df['latitude'].mean(), df['longitude'].mean()]
base_map = folium.Map(location=map_center, zoom_start=13)

# Nos quedamos con el 10% más caro
caros = df[df['price'] >= df['price'].quantile(0.9)]
heat_data = caros[['latitude', 'longitude']].values.tolist()

# Creamos el mapa
HeatMap(heat_data, radius=5, blur=4, max_zoom=1).add_to(base_map)
base_map
Out[37]:
Make this Notebook Trusted to load map: File -> Trust Notebook


Mapa de calor del total de las propiedades ofrecidas:

In [38]:
# Zoom y centro iniciales del mapa
map_center = [df['latitude'].mean(), df['longitude'].mean()]
base_map = folium.Map(location=map_center, zoom_start=13)

# Todas las propiedades
heat_data = df[['latitude', 'longitude']].values.tolist()

# Creamos el mapa
HeatMap(heat_data, radius=5, blur=4, max_zoom=1).add_to(base_map)
base_map
Out[38]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [ ]: